using System.Linq;
using System.Collections.Generic;
using System;
using System.Security.Permissions;
using System.Threading;
using System.Windows;

using Caliburn.Micro;

using Haushaltsbuch.Events;

namespace Haushaltsbuch.Extensions
{
    public static class ActionMessageExtensions
    {
        private static Action<ActionExecutionContext> oldPrepareContext;

        private static LogonHandler logonHandler;

        public static void AddActionPermissionHandling(IEventAggregator eventAggregator)
        {
            logonHandler = new LogonHandler(eventAggregator);
            oldPrepareContext = ActionMessage.PrepareContext;

            ActionMessage.ApplyAvailabilityEffect = ApplyAvailabilityEffect;
            ActionMessage.PrepareContext = PrepareContext;
        }

        private static void PrepareContext(ActionExecutionContext context)
        {
            oldPrepareContext(context);

            EventHandler onUserChanged = null;
            onUserChanged = (o, e) => context.Message.UpdateAvailability();

            logonHandler.CurrentUserChanged += onUserChanged;

            context.Disposing += (sender, args) => { logonHandler.CurrentUserChanged -= onUserChanged; };
        }

        private static bool ApplyAvailabilityEffect(ActionExecutionContext context)
        {
            var source = context.Source;
            if (ConventionManager.HasBinding(source, UIElement.IsEnabledProperty))
                return source.IsEnabled;

            bool canExecute = true;
            if (context.CanExecute != null)
                canExecute = context.CanExecute();

            if (canExecute)
            {
                var attributes = context.Method.GetAttributes<PrincipalPermissionAttribute>(true).ToArray();

                if (!attributes.Any())
                    canExecute = true;
                else
                {
                    var roles = attributes.Select(a => a.Role).ToArray();
                    canExecute = roles.Any(role => Thread.CurrentPrincipal.IsInRole(role));
                }
            }

            source.IsEnabled = canExecute;

            return canExecute;
        }

        #region Nested type: LogonHandler

        private sealed class LogonHandler : IHandle<CurrentUserChangedEvent>
        {
            public LogonHandler(IEventAggregator eventAggregator)
            {
                eventAggregator.Subscribe(this);
            }

            private void OnCurrentUserChanged()
            {
                EventHandler handler = this.CurrentUserChanged;
                if (handler != null) handler(this, EventArgs.Empty);
            }

            #region IHandle<CurrentUserChangedMessage> Members

            public void Handle(CurrentUserChangedEvent @event)
            {
                this.OnCurrentUserChanged();
            }

            #endregion

            public event EventHandler CurrentUserChanged;
        }

        #endregion
    }
}